import {
    addIcon, App, HeadingCache, ListItemCache, Modal, Notice,
    Plugin, PluginSettingTab, Pos, Setting, TagCache, TFile, TFolder, Vault
} from "obsidian";
import * as Path from "path";
import {JobDef} from "./common";
import {ListItem} from "./bullets";
import {SettingTab} from "./settings";
import {ExecuteUI} from "./ui";

interface PluginSettings {
    jobs: Array<JobDef>,
    // recursivelyJobs: Array<JobDef>,
    // headers: Array<[string, string, string, string, boolean]>,
    // locations: Array<string>,
    // outputs: Array<string>,
    // enables: Array<string>,
    scanRangePath: string,
    outputPath: string,
    populateEmptyTitle: boolean,
    titleLinks: boolean,
    ignoredTags: string,
    // includeSubHeadings: boolean,
}

const DEFAULT_SETTINGS: PluginSettings = {
    jobs: [new JobDef()],
    // recursivelyJobs: [new JobDef()],
    // headers: [""],
    // locations: [""],
    // outputs: [""],
    // enables: [""],
    scanRangePath: "1_Inbox/Diary",
    outputPath: "1_Inbox/Archive/tag-aggregations",
    populateEmptyTitle: false,
    titleLinks: true,
    ignoredTags: "#flomo,#cubox",
    // includeSubHeadings: true,
}

export default class TagsAggregatorPlugin extends Plugin {
    settings: PluginSettings

    async onload() {
        await this.loadSettings();
        const executeUI: Modal = new ExecuteUI(this.app, this);
        addIcon("tags-aggregator", `<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-tags-icon lucide-tags"><path d="m15 5 6.3 6.3a2.4 2.4 0 0 1 0 3.4L17 19"/><path d="M9.586 5.586A2 2 0 0 0 8.172 5H3a1 1 0 0 0-1 1v5.172a2 2 0 0 0 .586 1.414L8.29 18.29a2.426 2.426 0 0 0 3.42 0l3.58-3.58a2.426 2.426 0 0 0 0-3.42z"/><circle cx="6.5" cy="9.5" r=".5" fill="currentColor"/></svg>`);
        const ribbonIconEl = this.addRibbonIcon('tags-aggregator', 'Tags Aggregator', (evt: MouseEvent) => {
            executeUI.open();
        });
        this.addCommand({
            id: "execute-all-single-jobs",
            name: "Execute all single jobs",
            icon: "plus-square",
            callback: async () => {
                // const result = await this.aggregateTags("#经管", "1_Inbox/Diary", true);
                // console.log(result);
                // this.executeAllSingleJobs(this.settings.jobs);
                await this.executeAllSingleJobs(this.settings.jobs);
            },
        });
        this.addCommand({
            id: "execute-all-tags-job",
            name: "Execute all tags job",
            icon: "plus-square",
            callback: async () => {
                // const result = await this.aggregateTags("#经管", "1_Inbox/Diary", true);
                // console.log(result);
                // this.executeAllSingleJobs(this.settings.jobs);
                await this.executeAllTagsJob(this.settings.scanRangePath, this.settings.outputPath);
            },
        });
        // this.addCommand({
        //     id: "merge-daily-notes2",
        //     name: "generate jobs",
        //     icon: "plus-square",
        //     callback: () => {
        //         this.generateSubHeadingJobs();
        //     },
        // });

        // add settings tab
        this.addSettingTab(new SettingTab(this.app, this));
    }

    async loadSettings() {
        this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
    }

    async saveSettings() {
        await this.saveData(this.settings);
    }

    // headingToString(heading: HeadingCache): string {
    //     return "#".repeat(heading.level) + ` ${heading.heading}`
    // }

    getMarkdownFiles(startLocation: string) : TFile[] {
        const files: TFile[] = [];
        const folderOrFile = this.app.vault.getAbstractFileByPath(startLocation);
        // const folderOrFile = this.app.vault.getAbstractFileByPath("1_Inbox/Diary/2023-07-28.md");
        if (folderOrFile instanceof TFile) {
            files.push(folderOrFile);
        } else if (folderOrFile instanceof TFolder) {
            Vault.recurseChildren(folderOrFile, (file => {
                if (file instanceof TFile && file.extension === "md") {
                    files.push(file);
                }
            }));
        }
        files.sort((a, b)=>{
            if (a.basename.toLowerCase() > b.basename.toLowerCase()) return -1;
            if (a.basename.toLowerCase() < b.basename.toLowerCase()) return 1;
            return 0;
        });
        return files;
    }

    async writeToOutputPath(outputPath: string, headingText: string, content: string, openFile: boolean = true) {
        let outputFilePath: string;
        if (outputPath.endsWith(".md")) {
            outputFilePath = outputPath;
        } else {
            outputFilePath = `${outputPath}/${headingText}.md`;
        }
        if (!await this.app.vault.adapter.exists(Path.dirname(outputFilePath))) {
            await this.app.vault.createFolder(Path.dirname(outputFilePath));
        }

        await this.writeAndOpenFile(
            this.app,
            outputFilePath,
            content,
            openFile
        );
    }

    async writeAndOpenFile(app: App, outputFileName: string, text: string, openFile: boolean) {
        await app.vault.adapter.write(outputFileName, text);
        if (!openFile) return;

        let fileIsAlreadyOpened = false;
        app.workspace.iterateAllLeaves((leaf) => {
            if (
                leaf.getDisplayText() != "" &&
                outputFileName.startsWith(leaf.getDisplayText())
            ) {
                fileIsAlreadyOpened = true;
            }
        });
        if (!fileIsAlreadyOpened) {
            const newPane = app.workspace.getLeavesOfType("empty").length == 0;
            if (newPane) {
                app.workspace.openLinkText(outputFileName, "/", true);
            } else {
                const file = app.vault.getAbstractFileByPath(outputFileName);

                if (file instanceof TFile) {
                    await app.workspace
                        .getLeavesOfType("empty")[0]
                        .openFile(file);
                } else {
                    app.workspace.openLinkText(outputFileName, "/", true);
                }
            }
        }
    }

    buildTree(data: ListItem[], parentId: number = null, level: number = 1): ListItem[] {
        return data
            .filter(item => item.parentId === parentId)
            .map(item => ({
                ...item,
                level: level,
                // children: this.buildTree(data, item.position.start.line)
                children: this.buildTree(data, item.startLine, level + 1)
            }));
    }

    // visistListItemTree(tree: ListItemCache[], id: number) : ListItemCache | null {
    //     // 在自己这一层找
    //     for (const listItem of tree){
    //         if (listItem.position.start.line === id) {
    //             return listItem;
    //         }
    //     }
    //     // 自己这一层没有找到，到下一层找
    //     for (const listItem of tree) {
    //         //@ts-ignore
    //         if (listItem.children) {
    //             //@ts-ignore
    //             return this.visistListItemTree(listItem.children, id);
    //         }
    //     }
    //     return null;
    // }
    // traverseTreeFindLeafNodes(node: ListItem, leafNodes: ListItem[]) {
    //     if (!node) return;
    //     // process node
    //
    //     if (node.children && node.children.length > 0) {
    //         node.children.forEach(child => this.traverseTreeFindLeafNodes(child, leafNodes)); // 递归子节点
    //     } else { // 叶子节点
    //         // console.log(node.id);
    //         leafNodes.push(node);
    //     }
    // }
    // traverseTreeUpdateEndPosition(node: ListItem, endLine: number, endOffset: number) {
    //     if (!node) return;
    //     // process node
    //     // node.position.end = position.end;
    //     // node.position.end = position.end;
    //     node.endLine = endLine;
    //     node.endOffset = endOffset;
    //     if (node.children && node.children.length > 0) {
    //         node.children.forEach(child => this.traverseTreeUpdateEndPosition(child, endLine, endOffset)); // 递归子节点
    //     }
    // }

    // traverseTreeFindNodeById(node: ListItem, id: number) : ListItem {
    //     if (!node) return;
    //     // process node
    //     if (node.id === id) {
    //         return node;
    //     }
    //     if (node.children && node.children.length > 0) {
    //         for (const child of node.children) {
    //             return this.traverseTreeFindNodeById(child, id);
    //         }
    //         // node.children.forEach(child => {return this.traverseDFS3(child, id)}); // 递归子节点
    //     }
    // }

    traverseTree(node: ListItem, callback: any, level: number = 0, ) {
        if (!node) return;
        if (callback(node, level)) {
            return; // 中断遍历
        } // 处理当前节点
        if (node.children && node.children.length > 0) {
            node.children.forEach(child => this.traverseTree(child, callback, level + 1)); // 递归子节点
        }
    }
    // traverseTreeOutput(node: ListItem, level: number = 0, output: string = ""): string {
    //     if (!node) return;
    //     console.log(node.text);
    //     // process node
    //     output += "\t".repeat(level) + node.text + "\n";
    //     if (node.children && node.children.length > 0) {
    //         for (const child of node.children) {
    //             const temp = this.traverseTreeOutput(child, level + 1, output);
    //             return temp;
    //         }
    //         // 到循环完所有子节点return
    //         return output;
    //         // node.children.forEach(child => {return this.traverseDFS3(child, id)}); // 递归子节点
    //     } else {
    //         // 到叶子节点return
    //         return output;
    //     }
    // }
    async aggregateByTag(expr: string, scanRangePaths: string[], enabled: boolean) {
        if (!enabled)
            return;
        let mergedContent = "";
        let files: TFile[] = [];
        for (const path of scanRangePaths) {
            files = files.concat(this.getMarkdownFiles(path.trim()));
        }
        // const files: TFile[] = this.getMarkdownFiles(scanRangePath);
        for (const file of files) {
            console.log(`processing ${file.path}`);

            const cacheItem = this.app.metadataCache.getCache(file.path);
            if (!cacheItem.tags) continue;

            let prevLine = -1;
            const fileContent = await this.app.vault.read(file);

            // const listItemMap: Map<number, ListItem> = new Map();

            // for (const rootNode of tree){
            //     // 对于每一颗树，找到其所有的叶子节点，然后经过比较pos.end.offset，找到最下面的那个叶子节点。
            //     // TODO: 先默认是数组最后一个就行
            //     const leafNodes: ListItem[] = [];
            //     this.traverseTreeFindLeafNodes(rootNode, leafNodes);
            //     console.log(leafNodes)
            //     const lastLeafNode = leafNodes.last();
            //     console.log(lastLeafNode);
            //     this.traverseTreeUpdateEndPosition(rootNode, lastLeafNode.endLine, lastLeafNode.endOffset);
            // }
            let tree = null;
            if (cacheItem.listItems) {
                // 先将Obsidian的listItemCache结构转换为自己的listItem结构
                const listItems: ListItem[] = [];
                for (const listItemCache of cacheItem.listItems) {
                    const listItemText = fileContent.substring(listItemCache.position.start.offset, listItemCache.position.end.offset);
                    const listItem = new ListItem(listItemCache, listItemText);
                    listItems.push(listItem);
                    // listItemMap.set(listItem.id, listItem);
                }
                // 构建listItem tree
                tree = this.buildTree(listItems);
            }

            for (let i = 0; i < cacheItem.tags.length; i++) {
                const tag = cacheItem.tags[i].tag;
                // 这里的func模板为：return #tag1 && #tag2; 其中expr = #tag1 && #tag2
                // 然后把上面expr = #tag1 && #tag2 中的两个tag给扒拉出来，并替换成item.includes("xxx")
                // 于是这里的func被解析为：return item.includes("#tag1") && item.includes("#tag2")
                // item = lineText
                const func = new Function('item', `return ${(expr + " ").replace(/(#.+?)\s+?/g, 'item.includes("$1")')}`);
                // if (tagText.trim() === tag.trim()) {
                // if (func(cacheItem)) {
                const tagPos = cacheItem.tags[i].position;

                if (prevLine == -1 || tagPos.start.line != prevLine) { // 新的一行，避免一行中有多个tag会被处理多遍情况。 // 有tag的一行才需要处理 // 这里的逻辑相当于是循环所有包含tag的行，然后每一行文本取出来再判断是否满足func条件
                    // let textContainsTag = null;
                    // TODO: 如果tag的pos落在bullets中间（如果是第一行bullets，也因为在行末，所以也是落在bullets中间），那就把整个bullets都提取出来
                    // 对于每个listItemCache，判断tag是否属于这个bullets
                    let tagInBullets = false;
                    if (cacheItem.listItems) {
                        // 开始遍历此篇笔记中的所有listItemCache结构。如果这条listItemCache和tag在同一行，并且tag在listItemCache的pos start到end之间（第二个条件似乎是多余的）
                        for (const listItemCache of cacheItem.listItems) {
                            if (listItemCache.position.start.line === tagPos.start.line // 第一个条件是必须tag和bullets在同一行
                                && (listItemCache.position.start.offset <= tagPos.start.offset && tagPos.end.offset <= listItemCache.position.end.offset)) { // 第二个条件才是tag在bullets中间
                                // 从之前的tree结构中找到与listItemCache对应的listItem结构
                                let listItem = null;
                                for (const rootNode of tree) {
                                    this.traverseTree(rootNode, (node: ListItem, level: number) => {
                                        if (node.id === listItemCache.position.start.line) {
                                            listItem = node;
                                            return true; // 中断遍历返回true
                                        }
                                    });
                                    // listItem = this.traverseTreeFindNodeById(rootNode, listItemCache.position.start.line);
                                    if (listItem) {
                                        break;
                                    }
                                }
                                if (listItem) { // 依据listItemCacha找到自己构建的listItem对象
                                    let listItemText = "";
                                    this.traverseTree(listItem, (node: ListItem, level: number) => {
                                        listItemText += "\t".repeat(level) + node.text + "\n";
                                    });
                                    // let listItemText = this.traverseTreeOutput(listItem);
                                    console.log(listItemText);
                                    // let listItemText = fileContent.substring(listItem.startOffset, listItem.endOffset);
                                    if (func(listItemText)) {
                                        if (this.settings.titleLinks) {
                                            const fileLink = `[[${file.basename}]]`;
                                            listItemText = ` ${fileLink}\n${listItemText.trim()}\n`;
                                        } else {
                                            listItemText = `${listItemText.trim()}\n`;
                                        }
                                        mergedContent += listItemText;
                                    }
                                    tagInBullets = true;
                                }
                            }
                        }
                    }
                    if (!tagInBullets) {
                        const lines = fileContent.split("\n");
                        let lineText = lines[tagPos.start.line]; // 这里只支持抽取被tag标注的单独一行，如果这个tag是在一个bullets中，那么要将整个bullets块都取出。
                        let nextLine = tagPos.start.line + 1; // 加强能力，对于普通行，其下一行如果是bullets，那么都作为一个block进行输出
                        while (nextLine < lines.length) {
                            const nextLineText = lines[nextLine];
                            if (nextLineText != "" && !nextLineText.startsWith("#") && (nextLineText.trim().startsWith("* ") || nextLineText.trim().startsWith("- "))){
                                lineText += "\n" + nextLineText;
                            } else {
                                break;
                            }
                            nextLine += 1;
                        }

                        if (func(lineText)) {
                            if (this.settings.titleLinks) {
                                const fileLink = `[[${file.basename}]]`;
                                lineText = `${fileLink}\n${lineText.trim()}\n`;
                            } else {
                                lineText = `${lineText.trim()}\n`;
                            }
                            mergedContent += lineText;
                        }
                    }
                    // for (const listItem of cacheItem.listItems) {
                    //     // const ttt = this.visistListItemTree(tree, listItem.position.start.line);
                    //     // console.log(ttt);
                    //     if (listItem.position.start.line === pos.start.line // 第一个条件是必须tag和bullets在同一行
                    //         && (listItem.position.start.offset <= pos.start.offset && pos.end.offset <= listItem.position.end.offset)) { // 第二个条件才是tag在bullets中间
                    //         let listItemText = fileContent.substring(listItem.position.start.offset, listItem.position.end.offset);
                    //         if (func(listItemText)) {
                    //             if (this.settings.titleLinks) {
                    //                 const fileLink = `[[${file.basename}]]`;
                    //                 listItemText = `${listItemText.trim()} ${fileLink} \n`;
                    //             } else {
                    //                 listItemText = `${listItemText.trim()}\n`;
                    //             }
                    //             mergedContent += listItemText;
                    //         }
                    //         // console.log(listItemText);
                    //     }
                    // }

                    prevLine = tagPos.start.line;
                }
                // }
            }
        }
        return mergedContent;
    }

    async executeSingeJob(job: JobDef, overriddenEnabled: boolean = false) {
        const jobEnabled = overriddenEnabled ? true : job.enabled;
        const content = await this.aggregateByTag(job.tagText, job.scanRangePath.split(","), jobEnabled);
        if (content) {
            await this.writeToOutputPath(job.outputPath, job.tagText, content, false);
            new Notice(`Done! outputPath: ${job.outputPath}`);
        } else {
            new Notice(`Done! Nothing found.`);
        }
    }

    async executeAllSingleJobs(jobs: JobDef[]) {
        for (const job of jobs) {
            await this.executeSingeJob(job);
        }
        new Notice(`Done! all ${jobs.length} jobs.`);
    }

    async executeAllTagsJob(scanRangePath: string, outputPath: string) {
        const tagsMap: Map<string, TagCache> = new Map();
        const files: TFile[] = this.getMarkdownFiles(scanRangePath);
        for (const file of files) {
            const cacheItem = this.app.metadataCache.getCache(file.path);
            if (cacheItem.tags) {
                for (let i = 0; i < cacheItem.tags.length; i++) {
                    const tag = cacheItem.tags[i].tag;
                    if (!tagsMap.has(tag)) {
                        tagsMap.set(tag, cacheItem.tags[i]);
                    }
                }
            }
        }

        for (let [tagText, tagCache] of tagsMap) {
            if (this.settings.ignoredTags.contains(tagText.trim())) {
                continue;
            }
            const job = new JobDef();
            job.tagText = tagText;
            job.scanRangePath = scanRangePath;
            job.outputPath = `${outputPath}/${tagText}.md`;
            job.enabled = true;
            await this.executeSingeJob(job);
            // const content = await this.aggregateByTag(job.tagText, job.scanRangePath, job.enabled);
            // if (content) {
            //     await this.writeToOutputPath(job.outputPath, job.tagText, content, false);
            //     new Notice(`Done! outputPath: ${job.outputPath}`);
            // }
        }
    }
}
